/*
	Rais_MatrixLED - refactoried version.
	以HT1632C为控制芯片的32x8 LED点阵屏驱动库(已重构)
	不支持对管脚RD的使用.
	
	This library refactoried the LED Matrix 32x8 with HT1632C libraries,
	it supports the single matrix and poly matrix.
	NOT supports the using of port RD.
	
	Created:	2020.6.12
	Author:		Raiscies.
	
	
	namespace: rais
	class:	MatrixLED<uint8_t N = 1>											//N is screen's number

	public methods:
	MatrixLED(uint8_t cs, uint8_t wr, uint8_t... datas)							//constructor
	uint8_t* operator[](uint8_t line)											//returns original matrix data
	void	operator()(uint8_t x, uint8_t y, bool state)						//LED unit setter
	bool	operator()(uint8_t x, uint8_t y)									//LED unit getter
	void	set(uint8_t x, uint8_t y, bool state)								//safe LED unit setter
	bool	get(uint8_t x, uint8_t y)											//safe LED unit getter
	void	clear(bool state = false)											//set the whole data to false or true
	void	fill(bool state, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)	//fill a rectangle of matrix
	void	setBrightness(uint8_t level = 15)									//brightness setter
	void	blink(bool state = true)											//enable/disable blinking 
	void	setSystem(bool state)												//enable/disable chip system
	uint8_t	screenNum()															//return screens number
	void	paint()																//painting
	
*/

#ifndef RAIS_MATRIX_LED_H
#define RAIS_MATRIX_LED_H

#include <Arduino.h>

//HT1632C chip's commands and mode id:
#define MODE_W		0x5	//101
#define MODE_R		0x6	//110
#define MODE_R_M_W	0x5	//101
#define MODE_CMD	0x4	//100

#define SYS_EN		0x1	//0000 0001
#define SYS_DIS		0x0	//0000 0000

#define LED_ON		0x3	//0000 0011
#define LED_OFF		0x2	//0000 0010

#define BLINK_ON	0x9	//0000 1001
#define BLINK_OFF	0x8	//0000 1000


namespace rais{

//matrix data struction:
using line32_t = uint8_t[4];		//a line of matrix with 32 units

//class define:
//N is screens number. Exactly, this number shouldn't bigger than 32.
template <uint8_t N = 1>		
class MatrixLED{
	line32_t data[N * 8];			//[line = N * 8][field = 4(each field saves 8 units data)]
	
	public:
	//pins:
	const uint8_t CS, WR;
	uint8_t DATA[N];
	
	template <class... DATA_PINS_T>
	MatrixLED(uint8_t cs, uint8_t wr, DATA_PINS_T... datas): CS(cs), WR(wr) {
		pinMode(CS, OUTPUT);
		pinMode(WR, OUTPUT);
		digitalWrite(CS, HIGH);
		digitalWrite(WR, HIGH);
		
		if(N > 0) {
			initDataPins(0, datas...);
		
			writeCmd(SYS_EN);
			writeCmd(LED_ON);
		}
	}
	
	//return original matrix data:
	//eg: instance[line][field]
	uint8_t* operator[](uint8_t line){return data[line];}
	
	//unit LED's getter & setter:
	//getter:
	bool operator()(uint8_t x, uint8_t y) const{
		return (data[y][x >> 3] >> (~x & B111)) & 1;
	}
	//setter:
	void operator()(uint8_t x, uint8_t y, bool state){
		state ? data[y][x >> 3] |=  (B10000000 >> (x & B111))
			  : data[y][x >> 3] &= ~(B10000000 >> (x & B111));	  
	}
	//safe getter:
	bool get(uint8_t x, uint8_t y) const{
		return (x < 32 && y < N * 8) ? operator()(x, y) : false;
	}
	//safe setter:
	void set(uint8_t x, uint8_t y, bool state){
		if(x < 32 && y < N * 8) operator()(x, y, state);
	}
	
	//clear screen - turning all the LEDs on or off:
	void clear(bool state = false){memset(data, state ? 0xff : 0, 32 * N); }
	
	void fill(bool state, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2){
		if(x1 > x2) {x1 ^= x2; x2 ^= x1; x1 ^= x2; }	//swap two variables
		if(y1 > y2) {y1 ^= y2; y2 ^= y1; y1 ^= y2; }	//swap two variables
		if(y2 >= N * 8) y2 = N * 8 - 1;
		
		uint32_t filter = 0xffffffff << x1 >> (~x2 & B11111);
		if(!state) filter = ~filter;
		for(uint8_t i = y1; i <= y2; i++) *reinterpret_cast<uint32_t*>(data[i]) &= filter;
	}
	
	//brightness setting, the range is 0 ~ 15.
	void setBrightness(uint8_t level = 15){writeCmd(B10100000 | (level > 15 ? 15 : level)); }
	
	//blinking state setting:
	void blink(bool state = true){writeCmd(state ? BLINK_ON : BLINK_OFF); }
	
	//chip system state setting:
	void setSystem(bool state){
		if(state){
			writeCmd(SYS_EN);
			paint();
		}else 
			writeCmd(SYS_DIS);
	}
	
	//return screen's number:
	constexpr uint8_t screenNum() const{return N;}
	
	//multiScreen version:
	//painting:
	void paint(){
		resetCS(false, MODE_W);
		//write addres 0000000
		for(uint8_t i = 0; i < N; i++) digitalWrite(DATA[i], LOW);
		for(uint8_t i = 0; i < 7; i++){
			digitalWrite(WR, LOW);
			digitalWrite(WR, HIGH);
		}
		
		//cache:
		line32_t cache[N * 8];
		memcpy(cache, data, N * 32);
		
		//paint:
		for(int8_t field = 3; field >= 0; field--){			//for fields
			for(int8_t line = 7; line >= 0; line--){		//for lines
				for(uint8_t i = 0; i < 8; i++){				//for counting
					for(uint8_t scn = 0; scn < N; scn++){	//for screen groups painting
						digitalWrite(DATA[scn], cache[scn << 3 | line][field] & 1);
						cache[scn << 3 | line][field] >>= 1;
					}
					digitalWrite(WR, LOW);
					digitalWrite(WR, HIGH);
				}
			}
		}
		resetCS(true);
	}
	
	
	private:
	
	//data pins initalize:
	void initDataPins(uint8_t idx, uint8_t data){
		DATA[idx] = data;
		pinMode(data, OUTPUT);
		digitalWrite(data, LOW);
	}
	template <class... DATA_PINS_T>
	void initDataPins(uint8_t idx, uint8_t data, DATA_PINS_T... datas){
		initDataPins(idx, data);
		if(idx < N - 1) initDataPins(idx + 1, datas...);
	}
	
	
	//multiScreen version:
	//reset CS pin's state and write a mode id, the id can be ignore.
	void resetCS(bool state, uint8_t id = 0){
		digitalWrite(CS, HIGH);
		if(!state){
			digitalWrite(CS, LOW);
			if(id != 0) for(int8_t i = 2; i >= 0; i--) writeAllDataPins(id >> i & 1);
		}
	}
	
	//multiScreen version:
	//write a command:
	void writeCmd(uint8_t cmd){
		resetCS(false, MODE_CMD);
		for(int8_t i = 7; i >= 0; i--) writeAllDataPins(cmd >> i & 1);
		
		//write a useless but necessary bit:
		digitalWrite(WR, LOW);
		digitalWrite(WR, HIGH);
		resetCS(true);
	}
	
	//write a bit to all data pins - for multiScreen.
	void writeAllDataPins(bool state){
		for(uint8_t i = 0; i < N; i++) digitalWrite(DATA[i], state);
		digitalWrite(WR, LOW);
		digitalWrite(WR, HIGH);
	}
	
};

//resetCS() - single screen's version:
template <>
void MatrixLED<1>::resetCS(bool state, uint8_t id){
	digitalWrite(CS, HIGH);
	if(!state){
		digitalWrite(CS, LOW);
		if(id != 0) for(int8_t i = 2; i >= 0; i--){
			digitalWrite(DATA[0], id >> i & 1);
			digitalWrite(WR, LOW);
			digitalWrite(WR, HIGH);
		}
	}
}

//paint() - single screen's version:
template <>
void MatrixLED<1>::paint(){
	resetCS(false, MODE_W);
	//write address 0000000
	digitalWrite(DATA[0], LOW);
	for(uint8_t i = 0; i < 7; i++){
		digitalWrite(WR, LOW);
		digitalWrite(WR, HIGH);
	}
	digitalWrite(WR, LOW);

	//paint:
	for(int8_t field = 3; field >= 0; field--){		//for fields
		for(int8_t line = 7; line >= 0; line--){	//for lines
			shiftOut(DATA[0], WR, LSBFIRST, data[line][field]);
		}
	}
	resetCS(true);
}

//writeCmd() - single screen's version:
template <>
void MatrixLED<1>::writeCmd(uint8_t cmd){
	resetCS(false, MODE_CMD);
	digitalWrite(WR, LOW);
	shiftOut(DATA[0], WR, MSBFIRST, cmd);
	
	//write a useless but necessary bit:
	digitalWrite(WR, LOW);
	digitalWrite(WR, HIGH);
	resetCS(true);
}


}//end namespace rais

#endif